/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/common.hpp>
#include <baja/math.hpp>
#include <journal/views/interfaces.h>
#include <baja/graphics.hpp>
#include <baja/graphics/scenes.hpp>
#include <baja/linear_interpolation.hpp>

namespace journal { namespace view {

namespace b = baja;
namespace m = baja::math;
namespace g = baja::graphics;
namespace gs = baja::graphics::scenes;

class slotted_page : public b::ibase, public std::enable_shared_from_this<slotted_page>
{
    // these offsets are used to make sure the disabled pages rotate faster than the enable ones
    // (to make sure they make room for them quick enough so we don't see pages waiting to enable hanging
    // for too long)
    // also there's a random factor to make things look more realistic
    const b::float32 c_randomDisableOffset;
    const b::float32 c_minimumDisableOffset;
    const b::float32 c_randomEnableOffset;

    enum class state
    {
        waitingToEnable,
        enabling,
        enabled,
        disabling,
        disabled,
        moving
    };

    slotted_page() : 
        _state(state::disabled), 
        _observerCookie(-1),
        c_randomEnableOffset(-100.f),
        c_randomDisableOffset(350.f),
        c_minimumDisableOffset(100.f)
    {}

    inline static bool compareWithError(const b::float32 left, const b::float32 right)
    {
        return (abs(left - right) < 0.2);
    }

public:
    friend class std::_Ref_count_obj<slotted_page>;
    static std::shared_ptr<slotted_page> create(
        const std::shared_ptr<islotted_page_container>& container,
        const std::shared_ptr<ipage>& pageView
        )
    {
        std::shared_ptr<slotted_page> slot = std::make_shared<slotted_page>();

        slot->_container = container;
        slot->_pageView = pageView;
        slot->_state = state::disabled;

        slot->addObserver(container->pageObserver());

        pageView->setVisible(false);
        container->sceneGraph()->addChild(pageView->sceneNode());

        return slot;
    }

    void addObserver(const std::shared_ptr<ipage_observer>& observer)
    { 
        BAJA_VERIFY(_observerCookie == -1);
        _observerCookie = _pageView->addObserver(observer);
    }

    void removeObserver()
    {
        BAJA_VERIFY(_observerCookie != -1);
        _pageView->removeObserver(_observerCookie);
        _observerCookie = -1;
    }

    void clear(const std::shared_ptr<gs::iscene_graph>& sceneGraph)
    {
        _container.lock()->sceneGraph()->removeChild(_pageView->sceneNode());
        BAJA_VERIFY(_observerCookie);
    }

    void enable(const m::point3d& position, const b::float32 delay = -1)
    {
        BAJA_VERIFY(_pageView);

        if (_state == state::disabled || _state == state::disabling)
        {
            _nextPosition = position;
            _state = state::waitingToEnable;
        }
        else if (_state == state::enabled || _state == state::enabling)
        {
            if (!compareWithError(position.x, _pageView->position().x) ||
                !compareWithError(position.y, _pageView->position().y))
            {
                // first disable it from the current position
                b::float32 t = constants::general::currentTime();
                // make disabling randomly faster
                this->setRotationTarget(90.f, c_randomDisableOffset); 
                _nextPosition = position;
                _state = state::moving;
            }
        }
        else if (_state == state::moving)
        {
            _nextPosition = position;
        }
        else if (_state == state::waitingToEnable)
        {
            _nextPosition = position;
        }
    }

    void disable()
    {
        BAJA_VERIFY(_pageView);

        if (_state != state::disabled && _state != state::disabling)
        {
            _state = state::disabling;
            this->setRotationTarget(90.f, c_randomDisableOffset);
        }
    }

    void setRotationTarget(const b::float32 angle, b::float32 offset)
    {
        if (constants::animations::enableAnimations())
        {
            b::float32 x = 0.f;

            if (offset != 0.f)
            {
                x = c_minimumDisableOffset * ((rand() % 100) / 100.f) + offset;
            }

            b::float32 t = constants::general::currentTime();
            _tvi = b::linear_interpolation(t, t + constants::animations::rotationDuration() - x, _pageView->rotation().x, angle);
        }
        else
        {
            _pageView->setRotation(m::point3d(angle, 0.f, 0.f));
        }
    }

    bool applyRotation()
    {
        if (constants::animations::enableAnimations())
        {
            b::float32 rotationX = _pageView->rotation().x;

            if (rotationX != _tvi.b())
            {
                rotationX = _tvi.apply(constants::general::currentTime());

                _pageView->setRotation(m::point3d(rotationX, 0.f, 0.f));
                return rotationX == _tvi.b();
            }
            else
            {
                return true;
            }
        }
        else
        {
            return true;
        }
    }

    bool updatePageState()
    {
        bool returnValue = false;

        if (_state == state::waitingToEnable)
        {
            size_t slotPage = _container.lock()->checkSlot(_nextPosition);
            if (slotPage == _pageView->model()->pageNumber())
            {
                _state = state::enabling;
                this->setRotationTarget(0.f, c_randomEnableOffset);
            }
            else if (slotPage == NO_PAGE)
            {
                _state = state::enabling;
                _pageView->setVisible(true);

                _container.lock()->freeSlot(_pageView->position());
                _container.lock()->claimSlot(_nextPosition, _pageView->model()->pageNumber());
                _pageView->setPosition(_nextPosition);

                _pageView->setRotation(m::point3d(-90.f, 0.f, 0.f));
                this->setRotationTarget(0.f, -c_randomEnableOffset);
            }
        }
        else if (_state == state::enabling)
        {
            if (this->applyRotation())
            {
                _state = state::enabled;
            }
        }
        else if (_state == state::moving)
        {
            if (this->applyRotation())
            {
                _state = state::waitingToEnable;
                _pageView->setVisible(false);
            }
        }
        else if (_state == state::disabled)
        {
            BAJA_ASSERT(false, "we should never be disabled since our caller should be removing us");
        }
        else if (_state == state::disabling)
        {
            if (this->applyRotation())
            {
                _state = state::disabled;
                _container.lock()->sceneGraph()->removeChild(_pageView->sceneNode());
                BAJA_VERIFY(_observerCookie != -1);
                _pageView->removeObserver(_observerCookie);
                _observerCookie = -1;
                _container.lock()->freeSlot(_pageView->position());
                returnValue = true;  // delete
            }
        }
        else if (_state == state::enabled)
        {
            // nothing to do
        }

        return returnValue;
    }

    std::shared_ptr<ipage> pageView() { return _pageView; }

private:
    state _state;
    std::shared_ptr<ipage> _pageView;

    b::int32 _observerCookie;
    m::point3d _nextPosition;
    b::linear_interpolation _tvi;

    std::weak_ptr<islotted_page_container> _container;
};

}} // namespace journal::view